import { SetMetadata, applyDecorators } from "@nestjs/common";
import { PERMISSION_KEY } from "src/constant";

type PermissionInfo = {
    name: string;
    description: string;
    code: string;
};

type ObjectPermInfo = Record<string, PermissionInfo>;
type TuplePermInfo = ReadonlyArray<PermissionInfo>;

type TupleToObject<T extends string, P extends TuplePermInfo> = {
    [K in Uppercase<P[number]["code"]>]: `${T}:${Lowercase<K>}`;
};

type AddPrefixToObjectValue<T extends string, P extends ObjectPermInfo> = {
    [K in keyof P as K extends string ? Uppercase<K> : never]: `${T}:${P[K]["code"]}`;
};

let permissions: Record<PermissionInfo["code"], PermissionInfo> = {};

export function definePermission<T extends string, U extends TuplePermInfo>(prefix: T, actions: U): TupleToObject<T, U>;

export function definePermission<T extends string, U extends ObjectPermInfo>(
    prefix: T,
    actionMap: U,
): AddPrefixToObjectValue<T, U>;

export function definePermission(prefix: string, actions: ObjectPermInfo | TuplePermInfo) {
    if (actions instanceof Array) {
        actions.forEach((item) => {
            permissions[`${prefix}:${item.code}`] = {
                ...item,
                code: `${prefix}:${item.code}`,
            };
        });

        return actions.reduce((prev, action) => {
            const code = `${prefix}:${action.code}`;
            permissions[code] = {
                ...action,
                code,
            };
            prev[action.code.toUpperCase()] = code;
            return prev;
        }, {});
    } else {
        return Object.keys(actions).reduce((prev, action) => {
            actions[action].code = `${prefix}:${actions[action].code}`;
            permissions[actions[action].code] = { ...actions[action] };

            prev[action.toUpperCase()] = actions[action].code;
            return prev;
        }, {});
    }
}

export function UsePermGuard(...code: string[]) {
    return applyDecorators(SetMetadata(PERMISSION_KEY, code));
}

export const getDefinePermissions = () => Object.values(permissions);
